<template>
    <div class="video-uploader">
        <el-upload
            class="upload-demo"
            ref="uploadRef"
            :action="uploadAction"
            :auto-upload="true"
            :before-upload="beforeUpload"
            :on-change="handleFileChange"
            :on-success="handleSuccess"
            :on-error="handleError"
            :show-file-list="false"
            accept="video/mp4"
        >
            <el-button color="#f06423">{{ $t('course.resource_edit.add_dialog.label_4_btn') }}</el-button>
            <div slot="tip" class="el-upload__tip">{{ $t('course.resource_edit.add_dialog.label_11_placeholder') }}</div>
        </el-upload>

        <!-- 上传进度 -->
        <!-- <el-progress v-if="uploading && progress > 0" :percentage="progress" :format="formatProgress" status="success"></el-progress> -->
        <div class="progress-wrap" v-if="selectedFile && selectedFile.name">
            <div class="progress-content">
                <el-progress :percentage="progress" :stroke-width="22" :text-inside="true" status="success"></el-progress>
                <p v-if="selectedFile">{{ formatFileSize(selectedFile.size) }}</p>
            </div>
            <div class="file-name">
                <el-icon size="16" color="#4c4c4c"><VideoPlay /></el-icon>
                <span>{{ selectedFile.name || '--' }}</span>
                <el-button v-if="selectedFile && !isSuccess" color="#00A9CE" :loading="uploading" @click="startUpload" :disabled="uploading">
                    {{ uploading ? '上传中...' : '开始上传' }}
                </el-button>
                <el-button v-if="selectedFile && !isSuccess" color="#435764" @click="cancelUpload"> 取消上传 </el-button>
            </div>
        </div>
        <p v-else-if="videoUrl" class="file-name">
            <el-icon size="16" color="#4c4c4c"><VideoPlay /></el-icon>
            <span>{{ videoUrl.split('/').pop() }}</span>
        </p>
        <!-- 上传日志 -->
        <div v-if="uploadLogs.length" class="upload-logs" style="display: none">
            <h4>上传日志：</h4>
            <ul>
                <li v-for="(log, index) in uploadLogs" :key="index">{{ log }}</li>
            </ul>
        </div>
    </div>
</template>

<script lang="ts" setup>
import { ref, reactive, watch } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import SparkMD5 from 'spark-md5'
import axios from 'axios'
// 父组件传进来的参数
const props = defineProps<{
    videoUrl?: string
}>()
const videoUrl = ref(props.videoUrl)
watch(
    () => props.videoUrl,
    newValue => {
        // 处理数据
        videoUrl.value = newValue
    },
    { immediate: true }, // 初始化时立即执行一次
)

const emit = defineEmits(['relVideUrl'])
const uploadRef = ref<any>(null)
const selectedFile = ref<any>(null)
const fileHash = ref<string>('')
const chunkList = ref<any[]>([])
const progress = ref(0)
const uploading = ref(false)
const uploadLogs = ref<string[]>([])
const uploadAction = '/api/upload' // 后端接收地址
const isSuccess = ref(false) //是否上传成功
// const progressTimer = ref()
import uploadChunkService from '@/api/uploadChunk'
// 配置
const config = reactive({
    chunkSize: 5 * 1024 * 1024, // 5MB 每片
    retryCount: 3, // 重试次数
    concurrentUploads: 3, // 并发上传数
})

// 上传前检查
const beforeUpload = (file: any) => {
    // 限制文件类型
    if (!file.type.includes('video/mp4')) {
        ElMessage.error('请上传 MP4 格式视频')
        return false
    }

    // 记录选中的文件
    selectedFile.value = file
    return false // 阻止默认上传
}

// 文件变化时计算哈希
const handleFileChange = async (file: any) => {
    if (!file) return
    uploadLogs.value.push(`开始计算文件哈希 (${formatFileSize(file.size)})...`)
    fileHash.value = (await calculateFileHash(file.raw)) as string
    uploadLogs.value.push(`文件哈希计算完成: ${fileHash.value}`)
    prepareChunks(file.raw)
    // resetUpload()
}

// 计算文件哈希（分片读取以避免内存溢出）
const calculateFileHash = (file: any) => {
    return new Promise(resolve => {
        const spark = new SparkMD5.ArrayBuffer()
        const fileReader = new FileReader()
        const chunkSize = 2 * 1024 * 1024 // 每次读取 2MB
        let chunks = Math.ceil(file.size / chunkSize)
        let currentChunk = 0

        const processChunk = (chunk: any) => {
            fileReader.readAsArrayBuffer(chunk)
        }

        fileReader.onload = e => {
            spark.append(e.target?.result as ArrayBuffer)
            currentChunk++

            if (currentChunk < chunks) {
                processChunk(file.slice(currentChunk * chunkSize, (currentChunk + 1) * chunkSize))
            } else {
                const hash = spark.end()
                resolve(hash)
            }
        }

        fileReader.onerror = () => {
            ElMessage.error('文件哈希计算失败')
            resolve('')
        }

        processChunk(file.slice(0, chunkSize))
    })
}
// 取消上传
const canceling = ref(false) // 取消上传状态
const cancelUpload = () => {
    ElMessageBox.confirm('确定要取消上传吗？', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
    })
        .then(() => {
            canceling.value = true
            uploading.value = false
            // 重置上传状态
            resetUpload()
            // 通知服务器取消上传
            const params = {
                upload_id: uploadId.value,
            }
            uploadChunkService
                .chunkCancel(params)
                .then(res => {
                    ElMessage.info('上传已取消')
                })
                .finally(() => {})
        })
        .catch(() => {
            // 用户取消操作
        })
}

// 重置上传状态
const resetUpload = () => {
    isSuccess.value = false
    // 重置选中的文件
    selectedFile.value = null
    // 重置文件哈希
    fileHash.value = ''
    // 清空分片列表
    chunkList.value = []
    // 重置进度
    progress.value = 0
    // 停止上传状态
    uploading.value = false
    // 清空上传日志（如果保留日志功能）
    uploadLogs.value = []
    // 如果有上传引用，重置它
    if (uploadRef.value) {
        uploadRef.value.clearFiles()
    }
}
defineExpose({
    resetUpload,
})

// 准备分片
const uploadId = ref<string>() //上传ID
const prepareChunks = (file: any) => {
    const size = file.size
    const chunks = []
    let start = 0
    let end = 0

    uploadLogs.value.push(`准备分片上传，共 ${Math.ceil(size / config.chunkSize)} 片`)

    while (start < size) {
        end = Math.min(start + config.chunkSize, size)
        const chunk = file.slice(start, end)

        chunks.push({
            chunk,
            start,
            end,
            index: chunks.length,
            size: end - start,
            hash: fileHash.value,
            fileName: file.name,
            uploaded: false,
            retry: 0,
        })

        start = end
    }
    chunkList.value = chunks
}

// 开始上传
import pLimit from 'p-limit'
import pRetry from 'p-retry'
const startUpload = async () => {
    if (!selectedFile.value || uploading.value) return
    // 初始化分片上传任务
    const chunkInitParams = {
        filename: selectedFile.value.name,
        filesize: selectedFile.value.size,
        filetype: 'mp4',
        chunk_size: config.chunkSize,
        type: 'video',
    }
    const chunkInitResult = await uploadChunkService.chunkInit(chunkInitParams)
    uploadId.value = chunkInitResult.upload_id
    uploading.value = true
    progress.value = 0
    canceling.value = false
    // 计算初始进度
    updateProgress()
    const limit = pLimit(config.concurrentUploads)
    // 创建并执行受限的 Promise 数组
    // const uploadPromises = chunkList.value.map(file => limit(() => uploadNextChunk(file)))
    // 创建重试配置（最多重试2次，指数退避）
    const retryOptions = {
        retries: config.retryCount,
        onFailedAttempt: (error: any) => {
            console.log(`尝试 ${error.attemptNumber} 失败: ${error.message}`)
        },
    }
    const uploadPromises = chunkList.value.map(file => limit(() => pRetry(() => uploadNextChunk(file), retryOptions)))

    Promise.allSettled(uploadPromises).then(results => {
        const successes = results.filter(r => r.status === 'fulfilled')
        const failures = results.filter(r => r.status === 'rejected')

        if (failures.length == 0) {
            // 全部分片上传成功
            uploadLogs.value.push('所有分片上传完成，开始合并...')
            // 通知服务器合并分片
            const params = {
                upload_id: uploadId.value,
            }
            uploadChunkService
                .chunkComplete(params)
                .then(res => {
                    if (res.success) {
                        isSuccess.value = true
                        uploadLogs.value.push('文件合并成功')
                        console.log('文件合并成功', res.data)
                        ElMessage.success(res.data.message)
                        emit('relVideUrl', res.data.file_path)
                    } else {
                        uploadLogs.value.push('文件合并失败')
                        ElMessage.error('视频上传失败')
                    }
                    uploading.value = false
                })
                .finally(() => {})
        } else {
            console.log('成功的任务:', successes.length, successes)
            console.log('失败的任务:', failures.length, failures)
        }
    })
}

// 上传分片
const uploadNextChunk = async (chunkToUpload: any) => {
    // 检查是否取消上传
    if (canceling.value) return
    if (!chunkToUpload) return // 没有待上传的分片
    return new Promise(resolve => {
        try {
            // 检测该分片是否已存在
            uploadChunkService
                .chunkCheck(uploadId.value as string, chunkToUpload.index)
                .then(res => {
                    if (res.exists == false) {
                        // 创建可取消的请求
                        const source = axios.CancelToken.source()
                        chunkToUpload.cancelToken = source.token

                        const formData = new FormData()
                        formData.append('upload_id', uploadId.value as string)
                        formData.append('chunk_number', chunkToUpload.index)
                        formData.append('chunk', chunkToUpload.chunk)
                        uploadLogs.value.push(`上传分片 ${chunkToUpload.index + 1}/${chunkList.value.length} (${formatFileSize(chunkToUpload.size)})`)
                        uploadChunkService
                            .chunkUpload(formData, source.token, (progressEvent: any) => {
                                // 检查是否取消上传
                                if (canceling.value) {
                                    source.cancel('上传已取消')
                                    return
                                }
                                // 更新单片进度
                                if (progressEvent.lengthComputable) {
                                    const chunkProgress = Math.round((progressEvent.loaded * 100) / progressEvent.total)
                                    uploadLogs.value.push(`分片 ${chunkToUpload.index + 1} 进度: ${chunkProgress}%`)
                                }
                            })
                            .then(res => {
                                console.log(`上传成功:${chunkToUpload.index}`, res)
                                chunkToUpload.uploaded = true
                                updateProgress()
                                resolve({ success: true, chunkToUpload })
                            })
                            .catch(err => {
                                console.log('我是报错信息', err)
                            })
                            .finally(() => {})
                    } else {
                        resolve({ success: true, chunkToUpload })
                    }
                })
                .finally(() => {})
        } catch (error) {
            // 检查是否是主动取消
            if (axios.isCancel(error)) {
                console.log('上传被用户取消')
                return
            }
            // chunkToUpload.retry++
            // uploadLogs.value.push(`分片 ${chunkToUpload.index + 1} 上传失败 (尝试 ${chunkToUpload.retry}/${config.retryCount}): ${error.message}`)
            // if (chunkToUpload.retry < config.retryCount) {
            //     // 重试上传
            //     return uploadNextChunk(chunkToUpload)
            // } else {
            //     throw new Error(`分片 ${chunkToUpload.index + 1} 上传失败，已达到最大重试次数`)
            // }
        }
    })
}

// 更新总进度
const updateProgress = () => {
    const uploadedCount = chunkList.value.filter(chunk => chunk.uploaded).length
    progress.value = Math.round((uploadedCount / chunkList.value.length) * 100)

    // progressTimer.value = setInterval(() => {
    //     uploadChunkService.chunkProgress(uploadId.value).then(res => {
    //         progress.value = res.progress_percent
    //     }).finally(() => {})
    // }, 1000)
}

// 格式化进度显示
const formatProgress = (percentage: number) => {
    console.log(`${percentage}% (${formatFileSize(getUploadedSize())} / ${formatFileSize(selectedFile.value?.size || 0)})`)
    return `${percentage}% (${formatFileSize(getUploadedSize())} / ${formatFileSize(selectedFile.value?.size || 0)})`
}

// 计算已上传大小
const getUploadedSize = () => {
    return chunkList.value.reduce((total, chunk) => {
        return total + (chunk.uploaded ? chunk.size : 0)
    }, 0)
}

// 格式化文件大小
const formatFileSize = (bytes: number) => {
    if (bytes === 0) return '0 Bytes'

    const k = 1024
    const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']
    const i = Math.floor(Math.log(bytes) / Math.log(k))

    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}

// 处理上传成功
const handleSuccess = (response: any, file: any) => {
    ElMessage.success('上传成功')
    console.log('上传成功:', response)
}

// 处理上传失败
const handleError = (error: any, file: any) => {
    ElMessage.error('上传失败')
    console.error('上传失败:', error)
}
</script>

<style lang="less" scoped>
.upload-demo {
    margin-bottom: 10px;
}

.upload-logs {
    margin-top: 16px;
    max-height: 200px;
    overflow-y: auto;
    background-color: #f5f7fa;
    padding: 10px;
    border-radius: 4px;
}

.upload-logs h4 {
    margin-top: 0;
    margin-bottom: 8px;
    font-size: 14px;
}

.upload-logs ul {
    list-style: none;
    padding: 0;
    margin: 0;
    font-size: 12px;
}

.upload-logs li {
    margin-bottom: 4px;
    color: #606266;
}
// .video-uploader{
//     width: 100%
// }
.progress-wrap {
    .progress-content {
        width: 100%;
        margin-bottom: 10px;
        display: grid;
        grid-template-columns: 1fr 100px;
        gap: 10px;
    }
}
.el-upload__tip {
    margin-left: 10px;
}
.file-name {
    display: flex;
    flex-direction: row;
    justify-content: flex-start;
    align-items: center;
    span {
        margin-left: 5px;
        margin-right: 10px;
        line-height: 1.2;
    }
}
</style>
